/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <boost/thread.hpp>
#include <boost/foreach.hpp>

#include "Plugin.h"

using namespace std;
using namespace boost::asio;

namespace frontend {
namespace mpd {

log4cxx::LoggerPtr Plugin::logger(log4cxx::Logger::getLogger("frontend.mpd.Plugin"));
const int Plugin::DEFAULT_PORT = 6601;
const int Plugin::DEFAULT_THREAD_POOL_SIZE = 5;

Plugin::Plugin(backend::Plugin& backend, int port, int threadPoolSize)
: frontend::Plugin(backend),
  threadPoolSize(threadPoolSize),
  ios(),
  acceptor(ios, ip::tcp::endpoint(ip::tcp::v4(), port)),
  newConnection(new Connection(ios, backend, *this)),
  playlistVersion(2),
  playlistLength(backend.getPlaylist().size()),
  firstPlaylistEntryId(0)
{
	LOG4CXX_DEBUG(logger, "Create Plugin on port " << acceptor.local_endpoint().port()
			<< " using " << threadPoolSize << " threads");

	acceptor.async_accept(
			newConnection->getSocket(),
			boost::bind(
				&Plugin::onAccept,
				this,
				boost::asio::placeholders::error));
}

Plugin::~Plugin()
{
	LOG4CXX_TRACE(logger, "Destroy Plugin");
	playlistChangedSignalConnection.disconnect();
}

void Plugin::run()
{
	// Keep track of the playlist version
	playlistChangedSignalConnection = backend.connectPlaylistChanged(
			backend::Plugin::PlaylistChangedSlot(
				&Plugin::onPlaylistChanged,
				this));

	typedef boost::shared_ptr<boost::thread> ThreadPtr;
	vector<ThreadPtr> threads;
	threads.reserve(threadPoolSize);

	// Let each thread process ios.run()
	for(int i = 0; i < threadPoolSize; ++i)
		threads.push_back(ThreadPtr(new boost::thread(boost::bind(&io_service::run, &ios))));

	// Now wait until all threads are done
	for(int i = 0; i < threadPoolSize; ++i)
		threads[i]->join();
}

void Plugin::cancel()
{
	ios.stop();
}

int Plugin::getPlaylistVersion() const
{
	return playlistVersion;
}

int Plugin::getTrackIdFromPlaylistIndex(int index) const
{
	// TODO: This is wrong. When a track is added to the playlist, MPD assigns it with a new ID and increments its ID counter
	if(index < 0 || static_cast<size_t>(index) >= backend.getPlaylist().size())
		throw runtime_error("Invalid playlist index");
	return firstPlaylistEntryId + index;
}

int Plugin::getPlaylistIndexFromTrackId(int id) const
{
	// TODO: This is wrong. See getTrackIdFromPlaylistIndex()
	const int index = id - firstPlaylistEntryId;
	if(index < 0 || static_cast<size_t>(index) >= backend.getPlaylist().size())
		throw runtime_error("Invalid track ID");
	return index;
}

void Plugin::onPlaylistChanged()
{
	++playlistVersion;
	LOG4CXX_TRACE(logger, "Playlist changed to version " << playlistVersion);

	firstPlaylistEntryId += playlistLength;
	playlistLength = backend.getPlaylist().size();
}

void Plugin::onAccept(const boost::system::error_code& error)
{
	if(error)
		return;

	LOG4CXX_DEBUG(logger, "Accepted new connection from "
			<< newConnection->getSocket().remote_endpoint());

	newConnection->start();

	// Prepare next connection
	newConnection.reset(new Connection(ios, backend, *this));

	// Start new accept
	acceptor.async_accept(
			newConnection->getSocket(),
			boost::bind(&Plugin::onAccept,
				this,
				boost::asio::placeholders::error));
}

} // namespace mpd
} // namespace frontend

extern "C" frontend::mpd::Plugin* create(const boost::property_tree::ptree& config, backend::Plugin& backend)
{
	return new frontend::mpd::Plugin(backend,
			config.get("config.frontend.mpd.port", frontend::mpd::Plugin::DEFAULT_PORT),
			config.get("config.frontend.mpd.threads", frontend::mpd::Plugin::DEFAULT_THREAD_POOL_SIZE));
}

extern "C" void destroy(frontend::mpd::Plugin* p)
{
	delete p;
}
